package roborally;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import roborally.Commands.Command;
import roborally.Conditions.Condition;
import roborally.commandCombiners.CommandCombiner;
import roborally.commandCombiners.If;
import roborally.commandCombiners.Sequence;
import roborally.commandCombiners.While;

public class Program {
	//T = Statement
	private List<Statement> statementList;
	private List<Statement> currentLoops;
	
	public Program(List<Statement> statementList){
		for(Statement s: statementList){
			//It is useless to have a condition as a statement by it's own, without any command or commandcombiner alongside it.
			assert(s instanceof Command || s instanceof CommandCombiner);
		}
		this.setCurrentLoops(new ArrayList<Statement>());
		this.setStatementList(statementList);
	}
	
	public List<Statement> getCurrentLoops() {
		return currentLoops;
	}

	public void setCurrentLoops(List<Statement> currentLoops) {
		this.currentLoops = currentLoops;
	}

	public List<Statement> getStatementList() {
		return statementList;
	}

	public void setStatementList(List<Statement> statementList) {
		this.statementList = statementList;
	}
	
	public static void executeProgram(Robot robot, Program program){
		List<Statement> sList = program.getStatementList();
		for(Statement s : sList){
			System.out.println("---- " +s.getStringForm());
			s.execute(robot);
		}
	}

	public static void executeOneStepOfProgram(Robot robot, Program program) {
		List<Statement> statementList = program.getStatementList();
		Statement nextStatement = statementList.get(0);
		/*
		 * If the next statement is just a normal command, execute it and remove it from the programs statementList.
		 */
		if(nextStatement instanceof Command){
			nextStatement.execute(robot);
			program.removeFirstStatement();
		}
		else if(nextStatement instanceof CommandCombiner){
			/*
			 * If the next statement is an If command:
			 * The if statement is removed and either the thenCommand or the elseCommand is added to the start of the statementList
			 * Depending on whether the condition was true or not.
			 * Then this method is recursively called.
			 */
			if(nextStatement instanceof If){
				Condition condition = (Condition) ((If) nextStatement).getCondition();
				program.removeFirstStatement();
				if(condition.validate(robot)){
					Statement thenStatement = ((If) nextStatement).getThenCommand();
					program.addStatementToStartOfList(thenStatement);
					Program.executeOneStepOfProgram(robot,program);
				}
				else{
					Statement elseStatement = ((If) nextStatement).getElseCommand();
					program.addStatementToStartOfList(elseStatement);
					Program.executeOneStepOfProgram(robot,program);
				}
			}
			/*
			 * If the next statement is a Sequence command:
			 * The sequence command is removed and all the commands in its list are added to the start of the statementList.
			 * This method is then recursively called.
			 */
			else if(nextStatement instanceof Sequence){
				program.removeFirstStatement();
				for(Statement s: ((Sequence) nextStatement).getCommandList()){
					program.addStatementToStartOfList(s);
				}
				Program.executeOneStepOfProgram(robot,program);
			}
			/*
			 * If the next statement is a While command:
			 * - An end command is added to the start of the list to mark the end of this while loop in the altered program.
			 * - The while command is copied into a buffer because it might have to be executed multiple times if the conditions stays true.
			 * - The while command is removed from the StatementList, it's body is added to the start of the list.
			 * - This method is recursively called.
			 */
			else if(nextStatement instanceof While){
				program.removeFirstStatement();
				program.getCurrentLoops().add(nextStatement);
				program.addStatementToStartOfList(new EndOfWhileLoop());
				program.addStatementToStartOfList(((While) nextStatement).getBody());
				Program.executeOneStepOfProgram(robot, program);
			}
			/*
			 * If the next statement marks the end of a while loop:
			 * - Remove the statement
			 * - Check the condition for that while loop:
			 * 	- if it still holds, add the while loops body again to the program
			 * 	- otherwise delete the while loop from the currentLoops.
			 * - continue the program recursively.
			 */
			else if(nextStatement instanceof EndOfWhileLoop){
				program.removeFirstStatement();
				While lastLoop = program.getLastCurrentLoop();
				if(lastLoop.getCondition().validate(robot)){
					program.addStatementToStartOfList(new EndOfWhileLoop());
					program.addStatementToStartOfList(((While) lastLoop).getBody());
				}
				else{
					program.removeLastCurrentLoop();
				}
				Program.executeOneStepOfProgram(robot, program);
			}
		}
	}
	
	/**
	 * Remove the last saved loop from the currentLoops list. This means that an EndOfWhileLoop object has been reached and the
	 * condition for this while loop is no longer true, so it needs to be removed.
	 */
	private void removeLastCurrentLoop(){
		this.getCurrentLoops().remove(this.getCurrentLoops().size()-1);
	}
	
	/**
	 * Get the last while loop.
	 */
	private While getLastCurrentLoop(){
		return (While) this.getCurrentLoops().get(this.getCurrentLoops().size()-1);
	}
	
	/**
	 * Remove the first statement from the statementList.
	 */
	private void removeFirstStatement(){
		this.statementList.remove(0);
	}
	
	/**
	 * Add the given statement to the start of the statementList.
	 * @param statement
	 */
	private void addStatementToStartOfList(Statement statement){
		this.statementList.add(0,statement);
	}
	
	/**
	 * Save the given program as a file to the given path.
	 * Unfortunately we were out of time to do this as we had no clue how these things worked.
	 * We managed to learn part of it (as shown in loadProgramFromFile) but were unable to complete it.
	 */
	public static int saveProgramToFile(Program program, String path) {
		throw new UnsupportedOperationException();
	}

	/**
	 * Load the program-file that is located at the given path.
	 */
	public static Program loadProgramFromFile(String path) {
		/*
		 * Currently we can print a program to the console, but we are unfortunately out of time to parse the text
		 * and convert it to a working program.
		 */
			try{
		        System.out.println(System.getProperty("user.dir"));
		        FileInputStream fstream = new FileInputStream(System.getProperty("user.dir") + "\\cowboy.prog");

		        //Open
		        DataInputStream in = new DataInputStream(fstream);
		        BufferedReader br = new BufferedReader(new InputStreamReader(in));
		        String strLine;
	
		        while ((strLine = br.readLine()) != null)   {
		            // Print the content on the console
		            System.out.println (strLine);
		        }
		        //Close
		        in.close();
		    } 
			catch (Exception e){//Catch exception if any
		            System.err.println("Error: " + e.getMessage());
		    }
			return null;
	}
}
